/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2002,2004, 2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xerces.internal.dom;


import java.io.IOException;
import java.util.ArrayList;
import java.io.StringReader;
import java.util.Vector;

import com.sun.org.apache.xerces.internal.impl.Constants;
import com.sun.org.apache.xerces.internal.impl.RevalidationHandler;
import com.sun.org.apache.xerces.internal.impl.dtd.DTDGrammar;
import com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDDescription;
import com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator;
import com.sun.org.apache.xerces.internal.impl.dv.XSSimpleType;
import com.sun.org.apache.xerces.internal.impl.xs.util.SimpleLocator;
import com.sun.org.apache.xerces.internal.parsers.XMLGrammarPreparser;
import com.sun.org.apache.xerces.internal.util.AugmentationsImpl;
import com.sun.org.apache.xerces.internal.util.NamespaceSupport;
import com.sun.org.apache.xerces.internal.util.SymbolTable;
import com.sun.org.apache.xerces.internal.util.XML11Char;
import com.sun.org.apache.xerces.internal.util.XMLChar;
import com.sun.org.apache.xerces.internal.util.XMLGrammarPoolImpl;
import com.sun.org.apache.xerces.internal.util.XMLSymbols;
import com.sun.org.apache.xerces.internal.xni.Augmentations;
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
import com.sun.org.apache.xerces.internal.xni.QName;
import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
import com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler;
import com.sun.org.apache.xerces.internal.xni.XMLLocator;
import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
import com.sun.org.apache.xerces.internal.xni.XMLString;
import com.sun.org.apache.xerces.internal.xni.XNIException;
import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarDescription;
import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarPool;
import com.sun.org.apache.xerces.internal.xni.parser.XMLComponent;
import com.sun.org.apache.xerces.internal.xni.parser.XMLDocumentSource;
import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
import com.sun.org.apache.xerces.internal.xs.AttributePSVI;
import com.sun.org.apache.xerces.internal.xs.ElementPSVI;
import com.sun.org.apache.xerces.internal.xs.XSTypeDefinition;
import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMError;
import org.w3c.dom.DOMErrorHandler;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Entity;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;

/**
 * This class adds implementation for normalizeDocument method.
 * It acts as if the document was going through a save and load cycle, putting
 * the document in a "normal" form. The actual result depends on the features being set
 * and governing what operations actually take place. See setNormalizationFeature for details.
 * Noticeably this method normalizes Text nodes, makes the document "namespace wellformed",
 * according to the algorithm described below in pseudo code, by adding missing namespace
 * declaration attributes and adding or changing namespace prefixes, updates the replacement
 * tree of EntityReference nodes, normalizes attribute values, etc.
 * Mutation events, when supported, are generated to reflect the changes occuring on the
 * document.
 * See Namespace normalization for details on how namespace declaration attributes and prefixes
 * are normalized.
 *
 * NOTE: There is an initial support for DOM revalidation with XML Schema as a grammar.
 * The tree might not be validated correctly if entityReferences, CDATA sections are
 * present in the tree. The PSVI information is not exposed, normalized data (including element
 * default content is not available).
 *
 * @author Elena Litani, IBM
 * @author Neeraj Bajaj, Sun Microsystems, inc.
 * @version $Id: DOMNormalizer.java,v 1.9 2010-11-01 04:39:38 joehw Exp $
 * @xerces.experimental
 */
public class DOMNormalizer implements XMLDocumentHandler {

  //
  // constants
  //
  /**
   * Debug normalize document
   */
  protected final static boolean DEBUG_ND = false;
  /**
   * Debug namespace fix up algorithm
   */
  protected final static boolean DEBUG = false;
  /**
   * Debug document handler events
   */
  protected final static boolean DEBUG_EVENTS = false;

  /**
   * prefix added by namespace fixup algorithm should follow a pattern "NS" + index
   */
  protected final static String PREFIX = "NS";

  //
  // Data
  //
  protected DOMConfigurationImpl fConfiguration = null;
  protected CoreDocumentImpl fDocument = null;
  protected final XMLAttributesProxy fAttrProxy = new XMLAttributesProxy();
  protected final QName fQName = new QName();

  /**
   * Validation handler represents validator instance.
   */
  protected RevalidationHandler fValidationHandler;

  /**
   * symbol table
   */
  protected SymbolTable fSymbolTable;
  /**
   * error handler. may be null.
   */
  protected DOMErrorHandler fErrorHandler;

  /**
   * Cached {@link DOMError} impl.
   * The same object is re-used to report multiple errors.
   */
  private final DOMErrorImpl fError = new DOMErrorImpl();

  // Validation against namespace aware grammar
  protected boolean fNamespaceValidation = false;

  // Update PSVI information in the tree
  protected boolean fPSVI = false;

  /**
   * The namespace context of this document: stores namespaces in scope
   */
  protected final NamespaceContext fNamespaceContext = new NamespaceSupport();

  /**
   * Stores all namespace bindings on the current element
   */
  protected final NamespaceContext fLocalNSBinder = new NamespaceSupport();

  /**
   * list of attributes
   */
  protected final ArrayList fAttributeList = new ArrayList(5);

  /**
   * DOM Locator -  for namespace fixup algorithm
   */
  protected final DOMLocatorImpl fLocator = new DOMLocatorImpl();

  /**
   * for setting the PSVI
   */
  protected Node fCurrentNode = null;
  private QName fAttrQName = new QName();

  // attribute value normalization
  final XMLString fNormalizedValue = new XMLString(new char[16], 0, 0);

  /**
   * If the user stops the process, this exception will be thrown.
   */
  public static final RuntimeException abort = new RuntimeException();

  //DTD validator
  private XMLDTDValidator fDTDValidator;

  //Check if element content is all "ignorable whitespace"
  private boolean allWhitespace = false;

  // Constructor
  //

  public DOMNormalizer() {
  }


  /**
   * Normalizes document.
   * Note: reset() must be called before this method.
   */
  protected void normalizeDocument(CoreDocumentImpl document, DOMConfigurationImpl config) {

    fDocument = document;
    fConfiguration = config;

    // intialize and reset DOMNormalizer component
    //
    fSymbolTable = (SymbolTable) fConfiguration.getProperty(DOMConfigurationImpl.SYMBOL_TABLE);
    // reset namespace context
    fNamespaceContext.reset();
    fNamespaceContext.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);

    if ((fConfiguration.features & DOMConfigurationImpl.VALIDATE) != 0) {
      String schemaLang = (String) fConfiguration
          .getProperty(DOMConfigurationImpl.JAXP_SCHEMA_LANGUAGE);

      if (schemaLang != null && schemaLang.equals(Constants.NS_XMLSCHEMA)) {
        fValidationHandler =
            CoreDOMImplementationImpl.singleton.getValidator(XMLGrammarDescription.XML_SCHEMA);
        fConfiguration.setFeature(DOMConfigurationImpl.SCHEMA, true);
        fConfiguration.setFeature(DOMConfigurationImpl.SCHEMA_FULL_CHECKING, true);
        // report fatal error on DOM Level 1 nodes
        fNamespaceValidation = true;

        // check if we need to fill in PSVI
        fPSVI = ((fConfiguration.features & DOMConfigurationImpl.PSVI) != 0) ? true : false;
      }

      fConfiguration.setFeature(DOMConfigurationImpl.XERCES_VALIDATION, true);

      // reset ID table
      fDocument.clearIdentifiers();

      if (fValidationHandler != null)
      // reset schema validator
      {
        ((XMLComponent) fValidationHandler).reset(fConfiguration);
      }

    }

    fErrorHandler = (DOMErrorHandler) fConfiguration.getParameter(Constants.DOM_ERROR_HANDLER);
    if (fValidationHandler != null) {
      fValidationHandler.setDocumentHandler(this);
      fValidationHandler.startDocument(
          new SimpleLocator(fDocument.fDocumentURI, fDocument.fDocumentURI,
              -1, -1), fDocument.encoding, fNamespaceContext, null);

    }
    try {
      Node kid, next;
      for (kid = fDocument.getFirstChild(); kid != null; kid = next) {
        next = kid.getNextSibling();
        kid = normalizeNode(kid);
        if (kid != null) { // don't advance
          next = kid;
        }
      }

      // release resources
      if (fValidationHandler != null) {
        fValidationHandler.endDocument(null);
        CoreDOMImplementationImpl.singleton.releaseValidator(
            XMLGrammarDescription.XML_SCHEMA, fValidationHandler);
        fValidationHandler = null;
      }
    } catch (RuntimeException e) {
      if (e == abort) {
        return; // processing aborted by the user
      }
      throw e;    // otherwise re-throw.
    }

  }


  /**
   * This method acts as if the document was going through a save
   * and load cycle, putting the document in a "normal" form. The actual result
   * depends on the features being set and governing what operations actually
   * take place. See setNormalizationFeature for details. Noticeably this method
   * normalizes Text nodes, makes the document "namespace wellformed",
   * according to the algorithm described below in pseudo code, by adding missing
   * namespace declaration attributes and adding or changing namespace prefixes, updates
   * the replacement tree of EntityReference nodes,normalizes attribute values, etc.
   *
   * @param node Modified node or null. If node is returned, we need to normalize again starting on
   * the node returned.
   * @return the normalized Node
   */
  protected Node normalizeNode(Node node) {

    int type = node.getNodeType();
    boolean wellformed;
    fLocator.fRelatedNode = node;

    switch (type) {
      case Node.DOCUMENT_TYPE_NODE: {
        if (DEBUG_ND) {
          System.out.println("==>normalizeNode:{doctype}");
        }
        DocumentTypeImpl docType = (DocumentTypeImpl) node;
        fDTDValidator = (XMLDTDValidator) CoreDOMImplementationImpl.singleton
            .getValidator(XMLGrammarDescription.XML_DTD);
        fDTDValidator.setDocumentHandler(this);
        fConfiguration
            .setProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.XMLGRAMMAR_POOL_PROPERTY,
                createGrammarPool(docType));
        fDTDValidator.reset(fConfiguration);
        fDTDValidator.startDocument(
            new SimpleLocator(fDocument.fDocumentURI, fDocument.fDocumentURI,
                -1, -1), fDocument.encoding, fNamespaceContext, null);
        fDTDValidator
            .doctypeDecl(docType.getName(), docType.getPublicId(), docType.getSystemId(), null);
        //REVISIT: well-formness encoding info
        break;
      }

      case Node.ELEMENT_NODE: {
        if (DEBUG_ND) {
          System.out.println("==>normalizeNode:{element} " + node.getNodeName());
        }

        //do the name check only when version of the document was changed &
        //application has set the value of well-formed features to true
        if (fDocument.errorChecking) {
          if (((fConfiguration.features & DOMConfigurationImpl.WELLFORMED) != 0) &&
              fDocument.isXMLVersionChanged()) {
            if (fNamespaceValidation) {
              wellformed = CoreDocumentImpl
                  .isValidQName(node.getPrefix(), node.getLocalName(), fDocument.isXML11Version());
            } else {
              wellformed = CoreDocumentImpl
                  .isXMLName(node.getNodeName(), fDocument.isXML11Version());
            }
            if (!wellformed) {
              String msg = DOMMessageFormatter.formatMessage(
                  DOMMessageFormatter.DOM_DOMAIN,
                  "wf-invalid-character-in-node-name",
                  new Object[]{"Element", node.getNodeName()});
              reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_ERROR,
                  "wf-invalid-character-in-node-name");
            }
          }
        }
        // push namespace context
        fNamespaceContext.pushContext();
        fLocalNSBinder.reset();

        ElementImpl elem = (ElementImpl) node;
        if (elem.needsSyncChildren()) {
          elem.synchronizeChildren();
        }
        AttributeMap attributes =
            (elem.hasAttributes()) ? (AttributeMap) elem.getAttributes() : null;

        // fix namespaces and remove default attributes
        if ((fConfiguration.features & DOMConfigurationImpl.NAMESPACES) != 0) {
          // fix namespaces
          // normalize attribute values
          // remove default attributes
          namespaceFixUp(elem, attributes);

          if ((fConfiguration.features & DOMConfigurationImpl.NSDECL) == 0 && attributes != null) {
            for (int i = 0; i < attributes.getLength(); ++i) {
              Attr att = (Attr) attributes.getItem(i);
              if (XMLSymbols.PREFIX_XMLNS.equals(att.getPrefix()) ||
                  XMLSymbols.PREFIX_XMLNS.equals(att.getName())) {
                elem.removeAttributeNode(att);
                --i;
              }
            }
          }

        } else {
          if (attributes != null) {
            for (int i = 0; i < attributes.getLength(); ++i) {
              Attr attr = (Attr) attributes.item(i);
              //removeDefault(attr, attributes);
              attr.normalize();
              if (fDocument.errorChecking && (
                  (fConfiguration.features & DOMConfigurationImpl.WELLFORMED) != 0)) {
                isAttrValueWF(fErrorHandler, fError, fLocator, attributes, (AttrImpl) attr,
                    attr.getValue(), fDocument.isXML11Version());
                if (fDocument.isXMLVersionChanged()) {
                  wellformed = CoreDocumentImpl
                      .isXMLName(node.getNodeName(), fDocument.isXML11Version());
                  if (!wellformed) {
                    String msg = DOMMessageFormatter.formatMessage(
                        DOMMessageFormatter.DOM_DOMAIN,
                        "wf-invalid-character-in-node-name",
                        new Object[]{"Attr", node.getNodeName()});
                    reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_ERROR,
                        "wf-invalid-character-in-node-name");
                  }
                }
              }
            }
          }
        }

        if (fValidationHandler != null) {
          // REVISIT: possible solutions to discard default content are:
          //         either we pass some flag to XML Schema validator
          //         or rely on the PSVI information.
          fAttrProxy.setAttributes(attributes, fDocument, elem);
          updateQName(elem, fQName); // updates global qname
          // set error node in the dom error wrapper
          // so if error occurs we can report an error node
          fConfiguration.fErrorHandlerWrapper.fCurrentNode = node;
          fCurrentNode = node;
          // call re-validation handler
          fValidationHandler.startElement(fQName, fAttrProxy, null);
        }

        if (fDTDValidator != null) {
          // REVISIT: possible solutions to discard default content are:
          //         either we pass some flag to XML Schema validator
          //         or rely on the PSVI information.
          fAttrProxy.setAttributes(attributes, fDocument, elem);
          updateQName(elem, fQName); // updates global qname
          // set error node in the dom error wrapper
          // so if error occurs we can report an error node
          fConfiguration.fErrorHandlerWrapper.fCurrentNode = node;
          fCurrentNode = node;
          // call re-validation handler
          fDTDValidator.startElement(fQName, fAttrProxy, null);
        }

        // normalize children
        Node kid, next;
        for (kid = elem.getFirstChild(); kid != null; kid = next) {
          next = kid.getNextSibling();
          kid = normalizeNode(kid);
          if (kid != null) {
            next = kid;  // don't advance
          }
        }
        if (DEBUG_ND) {
          // normalized subtree
          System.out.println("***The children of {" + node.getNodeName() + "} are normalized");
          for (kid = elem.getFirstChild(); kid != null; kid = next) {
            next = kid.getNextSibling();
            System.out.println(kid.getNodeName() + "[" + kid.getNodeValue() + "]");
          }

        }

        if (fValidationHandler != null) {
          updateQName(elem, fQName); // updates global qname
          //
          // set error node in the dom error wrapper
          // so if error occurs we can report an error node
          fConfiguration.fErrorHandlerWrapper.fCurrentNode = node;
          fCurrentNode = node;
          fValidationHandler.endElement(fQName, null);
        }

        if (fDTDValidator != null) {
          updateQName(elem, fQName); // updates global qname
          //
          // set error node in the dom error wrapper
          // so if error occurs we can report an error node
          fConfiguration.fErrorHandlerWrapper.fCurrentNode = node;
          fCurrentNode = node;
          fDTDValidator.endElement(fQName, null);
        }

        // pop namespace context
        fNamespaceContext.popContext();

        break;
      }

      case Node.COMMENT_NODE: {
        if (DEBUG_ND) {
          System.out.println("==>normalizeNode:{comments}");
        }

        if ((fConfiguration.features & DOMConfigurationImpl.COMMENTS) == 0) {
          Node prevSibling = node.getPreviousSibling();
          Node parent = node.getParentNode();
          // remove the comment node
          parent.removeChild(node);
          if (prevSibling != null && prevSibling.getNodeType() == Node.TEXT_NODE) {
            Node nextSibling = prevSibling.getNextSibling();
            if (nextSibling != null && nextSibling.getNodeType() == Node.TEXT_NODE) {
              ((TextImpl) nextSibling).insertData(0, prevSibling.getNodeValue());
              parent.removeChild(prevSibling);
              return nextSibling;
            }
          }
        }//if comment node need not be removed
        else {
          if (fDocument.errorChecking && (
              (fConfiguration.features & DOMConfigurationImpl.WELLFORMED) != 0)) {
            String commentdata = ((Comment) node).getData();
            // check comments for invalid xml chracter as per the version
            // of the document
            isCommentWF(fErrorHandler, fError, fLocator, commentdata, fDocument.isXML11Version());
          }
        }//end-else if comment node is not to be removed.
        break;
      }
      case Node.ENTITY_REFERENCE_NODE: {
        if (DEBUG_ND) {
          System.out.println("==>normalizeNode:{entityRef} " + node.getNodeName());
        }

        if ((fConfiguration.features & DOMConfigurationImpl.ENTITIES) == 0) {
          Node prevSibling = node.getPreviousSibling();
          Node parent = node.getParentNode();
          ((EntityReferenceImpl) node).setReadOnly(false, true);
          expandEntityRef(parent, node);
          parent.removeChild(node);
          Node next = (prevSibling != null) ? prevSibling.getNextSibling() : parent.getFirstChild();
          // The list of children #text -> &ent;
          // and entity has a first child as a text
          // we should not advance
          if (prevSibling != null && next != null && prevSibling.getNodeType() == Node.TEXT_NODE &&
              next.getNodeType() == Node.TEXT_NODE) {
            return prevSibling;  // Don't advance
          }
          return next;
        } else {
          if (fDocument.errorChecking && (
              (fConfiguration.features & DOMConfigurationImpl.WELLFORMED) != 0) &&
              fDocument.isXMLVersionChanged()) {
            CoreDocumentImpl.isXMLName(node.getNodeName(), fDocument.isXML11Version());
          }
          // REVISIT: traverse entity reference and send appropriate calls to the validator
          // (no normalization should be performed for the children).
        }
        break;
      }

      case Node.CDATA_SECTION_NODE: {
        if (DEBUG_ND) {
          System.out.println("==>normalizeNode:{cdata}");
        }

        if ((fConfiguration.features & DOMConfigurationImpl.CDATA) == 0) {
          // convert CDATA to TEXT nodes
          Node prevSibling = node.getPreviousSibling();
          if (prevSibling != null && prevSibling.getNodeType() == Node.TEXT_NODE) {
            ((Text) prevSibling).appendData(node.getNodeValue());
            node.getParentNode().removeChild(node);
            return prevSibling; //don't advance
          } else {
            Text text = fDocument.createTextNode(node.getNodeValue());
            Node parent = node.getParentNode();
            node = parent.replaceChild(text, node);
            return text;  //don't advance

          }
        }

        // send characters call for CDATA
        if (fValidationHandler != null) {
          // set error node in the dom error wrapper
          // so if error occurs we can report an error node
          fConfiguration.fErrorHandlerWrapper.fCurrentNode = node;
          fCurrentNode = node;
          fValidationHandler.startCDATA(null);
          fValidationHandler.characterData(node.getNodeValue(), null);
          fValidationHandler.endCDATA(null);
        }

        if (fDTDValidator != null) {
          // set error node in the dom error wrapper
          // so if error occurs we can report an error node
          fConfiguration.fErrorHandlerWrapper.fCurrentNode = node;
          fCurrentNode = node;
          fDTDValidator.startCDATA(null);
          fDTDValidator.characterData(node.getNodeValue(), null);
          fDTDValidator.endCDATA(null);
        }
        String value = node.getNodeValue();

        if ((fConfiguration.features & DOMConfigurationImpl.SPLITCDATA) != 0) {
          int index;
          Node parent = node.getParentNode();
          if (fDocument.errorChecking) {
            isXMLCharWF(fErrorHandler, fError, fLocator, node.getNodeValue(),
                fDocument.isXML11Version());
          }
          while ((index = value.indexOf("]]>")) >= 0) {
            node.setNodeValue(value.substring(0, index + 2));
            value = value.substring(index + 2);

            Node firstSplitNode = node;
            Node newChild = fDocument.createCDATASection(value);
            parent.insertBefore(newChild, node.getNextSibling());
            node = newChild;
            // issue warning
            fLocator.fRelatedNode = firstSplitNode;
            String msg = DOMMessageFormatter.formatMessage(
                DOMMessageFormatter.DOM_DOMAIN,
                "cdata-sections-splitted",
                null);
            reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_WARNING,
                "cdata-sections-splitted");
          }

        } else if (fDocument.errorChecking) {
          // check well-formedness
          isCDataWF(fErrorHandler, fError, fLocator, value, fDocument.isXML11Version());
        }
        break;
      }

      case Node.TEXT_NODE: {
        if (DEBUG_ND) {
          System.out.println("==>normalizeNode(text):{" + node.getNodeValue() + "}");
        }
        // If node is a text node, we need to check for one of two
        // conditions:
        //   1) There is an adjacent text node
        //   2) There is no adjacent text node, but node is
        //      an empty text node.
        Node next = node.getNextSibling();
        // If an adjacent text node, merge it with this node
        if (next != null && next.getNodeType() == Node.TEXT_NODE) {
          ((Text) node).appendData(next.getNodeValue());
          node.getParentNode().removeChild(next);
          // We don't need to check well-formness here since we are not yet
          // done with this node.

          return node; // Don't advance;
        } else if (node.getNodeValue().length() == 0) {
          // If kid is empty, remove it
          node.getParentNode().removeChild(node);
        } else {
          // validator.characters() call and well-formness
          // Don't send characters or check well-formness in the following cases:
          // 1. entities is false, next child is entity reference: expand tree first
          // 2. comments is false, and next child is comment
          // 3. cdata is false, and next child is cdata

          short nextType = (next != null) ? next.getNodeType() : -1;
          if (nextType == -1 || !(((fConfiguration.features & DOMConfigurationImpl.ENTITIES) == 0 &&
              nextType == Node.ENTITY_NODE) ||
              ((fConfiguration.features & DOMConfigurationImpl.COMMENTS) == 0 &&
                  nextType == Node.COMMENT_NODE) ||
              ((fConfiguration.features & DOMConfigurationImpl.CDATA) == 0) &&
                  nextType == Node.CDATA_SECTION_NODE)) {
            if (fDocument.errorChecking && (
                (fConfiguration.features & DOMConfigurationImpl.WELLFORMED) != 0)) {
              isXMLCharWF(fErrorHandler, fError, fLocator, node.getNodeValue(),
                  fDocument.isXML11Version());
            }
            if (fValidationHandler != null) {
              fConfiguration.fErrorHandlerWrapper.fCurrentNode = node;
              fCurrentNode = node;
              fValidationHandler.characterData(node.getNodeValue(), null);
              if (DEBUG_ND) {
                System.out.println("=====>characterData()," + nextType);

              }
            }
            if (fDTDValidator != null) {
              fConfiguration.fErrorHandlerWrapper.fCurrentNode = node;
              fCurrentNode = node;
              fDTDValidator.characterData(node.getNodeValue(), null);
              if (DEBUG_ND) {
                System.out.println("=====>characterData()," + nextType);

              }
              if (allWhitespace) {
                allWhitespace = false;
                ((TextImpl) node).setIgnorableWhitespace(true);
              }
            }
          } else {
            if (DEBUG_ND) {
              System.out.println("=====>don't send characters()," + nextType);

            }
          }
        }
        break;
      }
      case Node.PROCESSING_INSTRUCTION_NODE: {

        //do the well-formed valid PI target name , data check when application has set the value of well-formed feature to true
        if (fDocument.errorChecking
            && (fConfiguration.features & DOMConfigurationImpl.WELLFORMED) != 0) {
          ProcessingInstruction pinode = (ProcessingInstruction) node;

          String target = pinode.getTarget();
          //1.check PI target name
          if (fDocument.isXML11Version()) {
            wellformed = XML11Char.isXML11ValidName(target);
          } else {
            wellformed = XMLChar.isValidName(target);
          }

          if (!wellformed) {
            String msg = DOMMessageFormatter.formatMessage(
                DOMMessageFormatter.DOM_DOMAIN,
                "wf-invalid-character-in-node-name",
                new Object[]{"Element", node.getNodeName()});
            reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_ERROR,
                "wf-invalid-character-in-node-name");
          }

          //2. check PI data
          //processing isntruction data may have certain characters
          //which may not be valid XML character
          isXMLCharWF(fErrorHandler, fError, fLocator, pinode.getData(),
              fDocument.isXML11Version());
        }
      }//end case Node.PROCESSING_INSTRUCTION_NODE

    }//end of switch
    return null;
  }//normalizeNode

  private XMLGrammarPool createGrammarPool(DocumentTypeImpl docType) {

    XMLGrammarPoolImpl pool = new XMLGrammarPoolImpl();

    XMLGrammarPreparser preParser = new XMLGrammarPreparser(fSymbolTable);
    preParser.registerPreparser(XMLGrammarDescription.XML_DTD, null);
    preParser.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.NAMESPACES_FEATURE, true);
    preParser.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.VALIDATION_FEATURE, true);
    preParser
        .setProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.XMLGRAMMAR_POOL_PROPERTY, pool);

    String internalSubset = docType.getInternalSubset();
    XMLInputSource is = new XMLInputSource(docType.getPublicId(), docType.getSystemId(), null);

    if (internalSubset != null) {
      is.setCharacterStream(new StringReader(internalSubset));
    }
    try {
      DTDGrammar g = (DTDGrammar) preParser.preparseGrammar(XMLGrammarDescription.XML_DTD, is);
      ((XMLDTDDescription) g.getGrammarDescription()).setRootName(docType.getName());
      is.setCharacterStream(null);
      g = (DTDGrammar) preParser.preparseGrammar(XMLGrammarDescription.XML_DTD, is);
      ((XMLDTDDescription) g.getGrammarDescription()).setRootName(docType.getName());

    } catch (XNIException e) {
    } catch (IOException e) {
    }

    return pool;
  }


  protected final void expandEntityRef(Node parent, Node reference) {
    Node kid, next;
    for (kid = reference.getFirstChild(); kid != null; kid = next) {
      next = kid.getNextSibling();
      parent.insertBefore(kid, reference);
    }
  }

  // fix namespaces
  // normalize attribute values
  // remove default attributes
  // check attribute names if the version of the document changed.

  protected final void namespaceFixUp(ElementImpl element, AttributeMap attributes) {
    if (DEBUG) {
      System.out.println("[ns-fixup] element:" + element.getNodeName() +
          " uri: " + element.getNamespaceURI());
    }

    // ------------------------------------
    // pick up local namespace declarations
    // <xsl:stylesheet xmlns:xsl="http://xslt">
    //   <!-- add the following via DOM
    //          body is bound to http://xslt
    //    -->
    //   <xsl:body xmlns:xsl="http://bound"/>
    //
    // ------------------------------------

    String value, name, uri, prefix;
    if (attributes != null) {

      // Record all valid local declarations
      for (int k = 0; k < attributes.getLength(); ++k) {
        Attr attr = (Attr) attributes.getItem(k);

        //do the name check only when version of the document was changed &
        //application has set the value of well-formed features to true
        if (fDocument.errorChecking && ((fConfiguration.features & DOMConfigurationImpl.WELLFORMED)
            != 0) &&
            fDocument.isXMLVersionChanged()) {
          //checkQName does checking based on the version of the document
          fDocument.checkQName(attr.getPrefix(), attr.getLocalName());
        }

        uri = attr.getNamespaceURI();
        if (uri != null && uri.equals(NamespaceContext.XMLNS_URI)) {
          // namespace attribute

          // "namespace-declarations" == false; Discard all namespace declaration attributes
          if ((fConfiguration.features & DOMConfigurationImpl.NSDECL) == 0) {
            continue;
          }

          value = attr.getNodeValue();
          if (value == null) {
            value = XMLSymbols.EMPTY_STRING;
          }

          // Check for invalid namespace declaration:
          if (fDocument.errorChecking && value.equals(NamespaceContext.XMLNS_URI)) {
            //A null value for locale is passed to formatMessage,
            //which means that the default locale will be used
            fLocator.fRelatedNode = attr;
            String msg = DOMMessageFormatter
                .formatMessage(DOMMessageFormatter.XML_DOMAIN, "CantBindXMLNS", null);
            reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_ERROR,
                "CantBindXMLNS");
          } else {
            // XML 1.0 Attribute value normalization
            // value = normalizeAttributeValue(value, attr);
            prefix = attr.getPrefix();
            prefix = (prefix == null ||
                prefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(prefix);
            String localpart = fSymbolTable.addSymbol(attr.getLocalName());
            if (prefix == XMLSymbols.PREFIX_XMLNS) { //xmlns:prefix

              value = fSymbolTable.addSymbol(value);
              if (value.length() != 0) {
                fNamespaceContext.declarePrefix(localpart, value);
              } else {
                // REVISIT: issue error on invalid declarations
                //          xmlns:foo = ""

              }
              //removeDefault (attr, attributes);
              continue;
            } else { // (localpart == fXmlnsSymbol && prefix == fEmptySymbol)  -- xmlns
              // empty prefix is always bound ("" or some string)
              value = fSymbolTable.addSymbol(value);
              fNamespaceContext.declarePrefix(XMLSymbols.EMPTY_STRING, value);
              //removeDefault (attr, attributes);
              continue;
            }
          }  // end-else: valid declaration
        } // end-if: namespace attribute
      }
    }

    // ---------------------------------------------------------
    // Fix up namespaces for element: per DOM L3
    // Need to consider the following cases:
    //
    // case 1: <xsl:stylesheet xmlns:xsl="http://xsl">
    // We create another element body bound to the "http://xsl" namespace
    // as well as namespace attribute rebounding xsl to another namespace.
    // <xsl:body xmlns:xsl="http://another">
    // Need to make sure that the new namespace decl value is changed to
    // "http://xsl"
    //
    // ---------------------------------------------------------
    // check if prefix/namespace is correct for current element
    // ---------------------------------------------------------

    uri = element.getNamespaceURI();
    prefix = element.getPrefix();

    // "namespace-declarations" == false? Discard all namespace declaration attributes
    if ((fConfiguration.features & DOMConfigurationImpl.NSDECL) == 0) {
      // no namespace declaration == no namespace URI, semantics are to keep prefix
      uri = null;
    } else if (uri != null) {  // Element has a namespace
      uri = fSymbolTable.addSymbol(uri);
      prefix = (prefix == null ||
          prefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(prefix);
      if (fNamespaceContext.getURI(prefix) == uri) {
        // The xmlns:prefix=namespace or xmlns="default" was declared at parent.
        // The binder always stores mapping of empty prefix to "".
      } else {
        // the prefix is either undeclared
        // or
        // conflict: the prefix is bound to another URI
        addNamespaceDecl(prefix, uri, element);
        fLocalNSBinder.declarePrefix(prefix, uri);
        fNamespaceContext.declarePrefix(prefix, uri);
      }
    } else { // Element has no namespace
      if (element.getLocalName() == null) {

        //  Error: DOM Level 1 node!
        if (fNamespaceValidation) {
          String msg = DOMMessageFormatter.formatMessage(
              DOMMessageFormatter.DOM_DOMAIN, "NullLocalElementName",
              new Object[]{element.getNodeName()});
          reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_FATAL_ERROR,
              "NullLocalElementName");
        } else {
          String msg = DOMMessageFormatter.formatMessage(
              DOMMessageFormatter.DOM_DOMAIN, "NullLocalElementName",
              new Object[]{element.getNodeName()});
          reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_ERROR,
              "NullLocalElementName");
        }

      } else { // uri=null and no colon (DOM L2 node)
        uri = fNamespaceContext.getURI(XMLSymbols.EMPTY_STRING);
        if (uri != null && uri.length() > 0) {
          // undeclare default namespace declaration (before that element
          // bound to non-zero length uir), but adding xmlns="" decl
          addNamespaceDecl(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING, element);
          fLocalNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
          fNamespaceContext.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
        }
      }
    }

    // -----------------------------------------
    // Fix up namespaces for attributes: per DOM L3
    // check if prefix/namespace is correct the attributes
    // -----------------------------------------
    if (attributes != null) {

      // clone content of the attributes
      attributes.cloneMap(fAttributeList);
      for (int i = 0; i < fAttributeList.size(); i++) {
        Attr attr = (Attr) fAttributeList.get(i);
        fLocator.fRelatedNode = attr;

        if (DEBUG) {
          System.out.println("==>[ns-fixup] process attribute: " + attr.getNodeName());
        }
        // normalize attribute value
        attr.normalize();
        value = attr.getValue();
        name = attr.getNodeName();
        uri = attr.getNamespaceURI();

        // make sure that value is never null.
        if (value == null) {
          value = XMLSymbols.EMPTY_STRING;
        }

        if (uri != null) {  // attribute has namespace !=null
          prefix = attr.getPrefix();
          prefix = (prefix == null ||
              prefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(prefix);
                    /*String localpart =*/
          fSymbolTable.addSymbol(attr.getLocalName());

          // ---------------------------------------
          // skip namespace declarations
          // ---------------------------------------
          // REVISIT: can we assume that "uri" is from some symbol
          // table, and compare by reference? -SG
          if (uri != null && uri.equals(NamespaceContext.XMLNS_URI)) {
            continue;
          }
          //---------------------------------------
          // check if value of the attribute is namespace well-formed
          //---------------------------------------
          if (fDocument.errorChecking && (
              (fConfiguration.features & DOMConfigurationImpl.WELLFORMED) != 0)) {
            isAttrValueWF(fErrorHandler, fError, fLocator, attributes, (AttrImpl) attr,
                attr.getValue(), fDocument.isXML11Version());
            if (fDocument.isXMLVersionChanged()) {
              boolean wellformed = CoreDocumentImpl
                  .isXMLName(attr.getNodeName(), fDocument.isXML11Version());
              if (!wellformed) {
                String msg = DOMMessageFormatter.formatMessage(
                    DOMMessageFormatter.DOM_DOMAIN,
                    "wf-invalid-character-in-node-name",
                    new Object[]{"Attribute", attr.getNodeName()});
                reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_ERROR,
                    "wf-invalid-character-in-node-name");
              }
            }
          }

          // ---------------------------------------
          // remove default attributes
          // ---------------------------------------
                    /*
                    if (removeDefault(attr, attributes)) {
                        continue;
                    }
                    */
          // XML 1.0 Attribute value normalization
          //value = normalizeAttributeValue(value, attr);

          // reset id-attributes
          ((AttrImpl) attr).setIdAttribute(false);

          uri = fSymbolTable.addSymbol(uri);

          // find if for this prefix a URI was already declared
          String declaredURI = fNamespaceContext.getURI(prefix);

          if (prefix == XMLSymbols.EMPTY_STRING || declaredURI != uri) {
            // attribute has no prefix (default namespace decl does not apply to attributes)
            // OR
            // attribute prefix is not declared
            // OR
            // conflict: attribute has a prefix that conficlicts with a binding
            //           already active in scope

            name = attr.getNodeName();
            // Find if any prefix for attributes namespace URI is available
            // in the scope
            String declaredPrefix = fNamespaceContext.getPrefix(uri);
            if (declaredPrefix != null && declaredPrefix != XMLSymbols.EMPTY_STRING) {

              // use the prefix that was found (declared previously for this URI
              prefix = declaredPrefix;
            } else {
              if (prefix != XMLSymbols.EMPTY_STRING && fLocalNSBinder.getURI(prefix) == null) {
                // the current prefix is not null and it has no in scope declaration

                // use this prefix
              } else {

                // find a prefix following the pattern "NS" +index (starting at 1)
                // make sure this prefix is not declared in the current scope.
                int counter = 1;
                prefix = fSymbolTable.addSymbol(PREFIX + counter++);
                while (fLocalNSBinder.getURI(prefix) != null) {
                  prefix = fSymbolTable.addSymbol(PREFIX + counter++);
                }

              }
              // add declaration for the new prefix
              addNamespaceDecl(prefix, uri, element);
              value = fSymbolTable.addSymbol(value);
              fLocalNSBinder.declarePrefix(prefix, value);
              fNamespaceContext.declarePrefix(prefix, uri);
            }

            // change prefix for this attribute
            attr.setPrefix(prefix);
          }
        } else { // attribute uri == null

          // XML 1.0 Attribute value normalization
          //value = normalizeAttributeValue(value, attr);

          // reset id-attributes
          ((AttrImpl) attr).setIdAttribute(false);

          if (attr.getLocalName() == null) {
            // It is an error if document has DOM L1 nodes.
            if (fNamespaceValidation) {
              String msg = DOMMessageFormatter.formatMessage(
                  DOMMessageFormatter.DOM_DOMAIN,
                  "NullLocalAttrName", new Object[]{attr.getNodeName()});
              reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_FATAL_ERROR,
                  "NullLocalAttrName");
            } else {
              String msg = DOMMessageFormatter.formatMessage(
                  DOMMessageFormatter.DOM_DOMAIN,
                  "NullLocalAttrName", new Object[]{attr.getNodeName()});
              reportDOMError(fErrorHandler, fError, fLocator, msg, DOMError.SEVERITY_ERROR,
                  "NullLocalAttrName");
            }
          } else {
            // uri=null and no colon
            // no fix up is needed: default namespace decl does not

            // ---------------------------------------
            // remove default attributes
            // ---------------------------------------
            // removeDefault(attr, attributes);
          }
        }
      }
    } // end loop for attributes
  }

  /**
   * Adds a namespace attribute or replaces the value of existing namespace
   * attribute with the given prefix and value for URI.
   * In case prefix is empty will add/update default namespace declaration.
   */

  protected final void addNamespaceDecl(String prefix, String uri, ElementImpl element) {
    if (DEBUG) {
      System.out.println("[ns-fixup] addNamespaceDecl [" + prefix + "]");
    }
    if (prefix == XMLSymbols.EMPTY_STRING) {
      if (DEBUG) {
        System.out.println("=>add xmlns=\"" + uri + "\" declaration");
      }
      element.setAttributeNS(NamespaceContext.XMLNS_URI, XMLSymbols.PREFIX_XMLNS, uri);
    } else {
      if (DEBUG) {
        System.out.println("=>add xmlns:" + prefix + "=\"" + uri + "\" declaration");
      }
      element.setAttributeNS(NamespaceContext.XMLNS_URI, "xmlns:" + prefix, uri);
    }
  }

  //
  // Methods for well-formness checking
  //


  /**
   * Check if CDATA section is well-formed
   *
   * @param isXML11Version = true if XML 1.1
   */
  public static final void isCDataWF(DOMErrorHandler errorHandler, DOMErrorImpl error,
      DOMLocatorImpl locator,
      String datavalue, boolean isXML11Version) {
    if (datavalue == null || (datavalue.length() == 0)) {
      return;
    }

    char[] dataarray = datavalue.toCharArray();
    int datalength = dataarray.length;

    // version of the document is XML 1.1
    if (isXML11Version) {
      // we need to check all chracters as per production rules of XML11
      int i = 0;
      while (i < datalength) {
        char c = dataarray[i++];
        if (XML11Char.isXML11Invalid(c)) {
          // check if this is a supplemental character
          if (XMLChar.isHighSurrogate(c) && i < datalength) {
            char c2 = dataarray[i++];
            if (XMLChar.isLowSurrogate(c2) &&
                XMLChar.isSupplemental(XMLChar.supplemental(c, c2))) {
              continue;
            }
          }
          String msg = DOMMessageFormatter.formatMessage(
              DOMMessageFormatter.XML_DOMAIN,
              "InvalidCharInCDSect",
              new Object[]{Integer.toString(c, 16)});
          reportDOMError(
              errorHandler,
              error,
              locator,
              msg,
              DOMError.SEVERITY_ERROR,
              "wf-invalid-character");
        } else if (c == ']') {
          int count = i;
          if (count < datalength && dataarray[count] == ']') {
            while (++count < datalength && dataarray[count] == ']') {
              // do nothing
            }
            if (count < datalength && dataarray[count] == '>') {
              // CDEndInContent
              String msg = DOMMessageFormatter
                  .formatMessage(DOMMessageFormatter.XML_DOMAIN, "CDEndInContent", null);
              reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
                  "wf-invalid-character");
            }
          }

        }
      }
    } // version of the document is XML 1.0
    else {
      // we need to check all chracters as per production rules of XML 1.0
      int i = 0;
      while (i < datalength) {
        char c = dataarray[i++];
        if (XMLChar.isInvalid(c)) {
          // check if this is a supplemental character
          if (XMLChar.isHighSurrogate(c) && i < datalength) {
            char c2 = dataarray[i++];
            if (XMLChar.isLowSurrogate(c2) &&
                XMLChar.isSupplemental(XMLChar.supplemental(c, c2))) {
              continue;
            }
          }
          // Note:  The key InvalidCharInCDSect from XMLMessages.properties
          // is being used to obtain the message and DOM error type
          // "wf-invalid-character" is used.  Also per DOM it is error but
          // as per XML spec. it is fatal error
          String msg = DOMMessageFormatter.formatMessage(
              DOMMessageFormatter.XML_DOMAIN,
              "InvalidCharInCDSect",
              new Object[]{Integer.toString(c, 16)});
          reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
              "wf-invalid-character");
        } else if (c == ']') {
          int count = i;
          if (count < datalength && dataarray[count] == ']') {
            while (++count < datalength && dataarray[count] == ']') {
              // do nothing
            }
            if (count < datalength && dataarray[count] == '>') {
              String msg = DOMMessageFormatter
                  .formatMessage(DOMMessageFormatter.XML_DOMAIN, "CDEndInContent", null);
              reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
                  "wf-invalid-character");
            }
          }

        }
      }
    } // end-else fDocument.isXMLVersion()

  } // isCDataWF

  /**
   * NON-DOM: check for valid XML characters as per the XML version
   *
   * @param isXML11Version = true if XML 1.1
   */
  public static final void isXMLCharWF(DOMErrorHandler errorHandler, DOMErrorImpl error,
      DOMLocatorImpl locator,
      String datavalue, boolean isXML11Version) {
    if (datavalue == null || (datavalue.length() == 0)) {
      return;
    }

    char[] dataarray = datavalue.toCharArray();
    int datalength = dataarray.length;

    // version of the document is XML 1.1
    if (isXML11Version) {
      //we need to check all characters as per production rules of XML11
      int i = 0;
      while (i < datalength) {
        if (XML11Char.isXML11Invalid(dataarray[i++])) {
          // check if this is a supplemental character
          char ch = dataarray[i - 1];
          if (XMLChar.isHighSurrogate(ch) && i < datalength) {
            char ch2 = dataarray[i++];
            if (XMLChar.isLowSurrogate(ch2) &&
                XMLChar.isSupplemental(XMLChar.supplemental(ch, ch2))) {
              continue;
            }
          }
          String msg = DOMMessageFormatter.formatMessage(
              DOMMessageFormatter.DOM_DOMAIN, "InvalidXMLCharInDOM",
              new Object[]{Integer.toString(dataarray[i - 1], 16)});
          reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
              "wf-invalid-character");
        }
      }
    } // version of the document is XML 1.0
    else {
      // we need to check all characters as per production rules of XML 1.0
      int i = 0;
      while (i < datalength) {
        if (XMLChar.isInvalid(dataarray[i++])) {
          // check if this is a supplemental character
          char ch = dataarray[i - 1];
          if (XMLChar.isHighSurrogate(ch) && i < datalength) {
            char ch2 = dataarray[i++];
            if (XMLChar.isLowSurrogate(ch2) &&
                XMLChar.isSupplemental(XMLChar.supplemental(ch, ch2))) {
              continue;
            }
          }
          String msg = DOMMessageFormatter.formatMessage(
              DOMMessageFormatter.DOM_DOMAIN, "InvalidXMLCharInDOM",
              new Object[]{Integer.toString(dataarray[i - 1], 16)});
          reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
              "wf-invalid-character");
        }
      }
    } // end-else fDocument.isXMLVersion()

  } // isXMLCharWF

  /**
   * NON-DOM: check if value of the comment is well-formed
   *
   * @param isXML11Version = true if XML 1.1
   */
  public static final void isCommentWF(DOMErrorHandler errorHandler, DOMErrorImpl error,
      DOMLocatorImpl locator,
      String datavalue, boolean isXML11Version) {
    if (datavalue == null || (datavalue.length() == 0)) {
      return;
    }

    char[] dataarray = datavalue.toCharArray();
    int datalength = dataarray.length;

    // version of the document is XML 1.1
    if (isXML11Version) {
      // we need to check all chracters as per production rules of XML11
      int i = 0;
      while (i < datalength) {
        char c = dataarray[i++];
        if (XML11Char.isXML11Invalid(c)) {
          // check if this is a supplemental character
          if (XMLChar.isHighSurrogate(c) && i < datalength) {
            char c2 = dataarray[i++];
            if (XMLChar.isLowSurrogate(c2) &&
                XMLChar.isSupplemental(XMLChar.supplemental(c, c2))) {
              continue;
            }
          }
          String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.XML_DOMAIN,
              "InvalidCharInComment",
              new Object[]{Integer.toString(dataarray[i - 1], 16)});
          reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
              "wf-invalid-character");
        } else if (c == '-' && i < datalength && dataarray[i] == '-') {
          String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.XML_DOMAIN,
              "DashDashInComment", null);
          // invalid: '--' in comment
          reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
              "wf-invalid-character");
        }
      }
    } // version of the document is XML 1.0
    else {
      // we need to check all chracters as per production rules of XML 1.0
      int i = 0;
      while (i < datalength) {
        char c = dataarray[i++];
        if (XMLChar.isInvalid(c)) {
          // check if this is a supplemental character
          if (XMLChar.isHighSurrogate(c) && i < datalength) {
            char c2 = dataarray[i++];
            if (XMLChar.isLowSurrogate(c2) &&
                XMLChar.isSupplemental(XMLChar.supplemental(c, c2))) {
              continue;
            }
          }
          String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.XML_DOMAIN,
              "InvalidCharInComment", new Object[]{Integer.toString(dataarray[i - 1], 16)});
          reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
              "wf-invalid-character");
        } else if (c == '-' && i < datalength && dataarray[i] == '-') {
          String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.XML_DOMAIN,
              "DashDashInComment", null);
          // invalid: '--' in comment
          reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
              "wf-invalid-character");
        }
      }

    } // end-else fDocument.isXMLVersion()

  } // isCommentWF

  /**
   * NON-DOM: check if attribute value is well-formed
   */
  public static final void isAttrValueWF(DOMErrorHandler errorHandler, DOMErrorImpl error,
      DOMLocatorImpl locator, NamedNodeMap attributes, Attr a, String value, boolean xml11Version) {
    if (a instanceof AttrImpl && ((AttrImpl) a).hasStringValue()) {
      isXMLCharWF(errorHandler, error, locator, value, xml11Version);
    } else {
      NodeList children = a.getChildNodes();
      //check each child node of the attribute's value
      for (int j = 0; j < children.getLength(); j++) {
        Node child = children.item(j);
        //If the attribute's child is an entity refernce
        if (child.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
          Document owner = a.getOwnerDocument();
          Entity ent = null;
          //search for the entity in the docType
          //of the attribute's ownerDocument
          if (owner != null) {
            DocumentType docType = owner.getDoctype();
            if (docType != null) {
              NamedNodeMap entities = docType.getEntities();
              ent = (Entity) entities.getNamedItemNS(
                  "*",
                  child.getNodeName());
            }
          }
          //If the entity was not found issue a fatal error
          if (ent == null) {
            String msg = DOMMessageFormatter.formatMessage(
                DOMMessageFormatter.DOM_DOMAIN, "UndeclaredEntRefInAttrValue",
                new Object[]{a.getNodeName()});
            reportDOMError(errorHandler, error, locator, msg, DOMError.SEVERITY_ERROR,
                "UndeclaredEntRefInAttrValue");
          }
        } else {
          // Text node
          isXMLCharWF(errorHandler, error, locator, child.getNodeValue(), xml11Version);
        }
      }
    }
  }


  /**
   * Reports a DOM error to the user handler.
   *
   * If the error is fatal, the processing will be always aborted.
   */
  public static final void reportDOMError(DOMErrorHandler errorHandler, DOMErrorImpl error,
      DOMLocatorImpl locator,
      String message, short severity, String type) {
    if (errorHandler != null) {
      error.reset();
      error.fMessage = message;
      error.fSeverity = severity;
      error.fLocator = locator;
      error.fType = type;
      error.fRelatedData = locator.fRelatedNode;

      if (!errorHandler.handleError(error)) {
        throw abort;
      }
    }
    if (severity == DOMError.SEVERITY_FATAL_ERROR) {
      throw abort;
    }
  }

  protected final void updateQName(Node node, QName qname) {

    String prefix = node.getPrefix();
    String namespace = node.getNamespaceURI();
    String localName = node.getLocalName();
    // REVISIT: the symbols are added too often: start/endElement
    //          and in the namespaceFixup. Should reduce number of calls to symbol table.
    qname.prefix = (prefix != null && prefix.length() != 0) ? fSymbolTable.addSymbol(prefix) : null;
    qname.localpart = (localName != null) ? fSymbolTable.addSymbol(localName) : null;
    qname.rawname = fSymbolTable.addSymbol(node.getNodeName());
    qname.uri = (namespace != null) ? fSymbolTable.addSymbol(namespace) : null;
  }


  /* REVISIT: remove this method if DOM does not change spec.
   * Performs partial XML 1.0 attribute value normalization and replaces
* attribute value if the value is changed after the normalization.
* DOM defines that normalizeDocument acts as if the document was going
* through a save and load cycle, given that serializer will not escape
* any '\n' or '\r' characters on load those will be normalized.
* Thus during normalize document we need to do the following:
* - perform "2.11 End-of-Line Handling"
* - replace #xD, #xA, #x9 with #x20 (white space).
* Note: This alg. won't attempt to resolve entity references or character entity
* references, since '&' will be escaped during serialization and during loading
* this won't be recognized as entity reference, i.e. attribute value "&foo;" will
* be serialized as "&amp;foo;" and thus after loading will be "&foo;" again.
   * @param value current attribute value
   * @param attr current attribute
   * @return String the value (could be original if normalization did not change
* the string)
   */
  final String normalizeAttributeValue(String value, Attr attr) {
    if (!attr.getSpecified()) {
      // specified attributes should already have a normalized form
      // since those were added by validator
      return value;
    }
    int end = value.length();
    // ensure capacity
    if (fNormalizedValue.ch.length < end) {
      fNormalizedValue.ch = new char[end];
    }
    fNormalizedValue.length = 0;
    boolean normalized = false;
    for (int i = 0; i < end; i++) {
      char c = value.charAt(i);
      if (c == 0x0009 || c == 0x000A) {
        fNormalizedValue.ch[fNormalizedValue.length++] = ' ';
        normalized = true;
      } else if (c == 0x000D) {
        normalized = true;
        fNormalizedValue.ch[fNormalizedValue.length++] = ' ';
        int next = i + 1;
        if (next < end && value.charAt(next) == 0x000A) {
          i = next; // skip following xA
        }
      } else {
        fNormalizedValue.ch[fNormalizedValue.length++] = c;
      }
    }
    if (normalized) {
      value = fNormalizedValue.toString();
      attr.setValue(value);
    }
    return value;
  }

  protected final class XMLAttributesProxy
      implements XMLAttributes {

    protected AttributeMap fAttributes;
    protected CoreDocumentImpl fDocument;
    protected ElementImpl fElement;

    protected final Vector fAugmentations = new Vector(5);


    public void setAttributes(AttributeMap attributes, CoreDocumentImpl doc, ElementImpl elem) {
      fDocument = doc;
      fAttributes = attributes;
      fElement = elem;
      if (attributes != null) {
        int length = attributes.getLength();

        fAugmentations.setSize(length);
        // REVISIT: this implementation does not store any value in augmentations
        //          and basically not keeping augs in parallel to attributes map
        //          untill all attributes are added (default attributes)
        for (int i = 0; i < length; i++) {
          fAugmentations.setElementAt(new AugmentationsImpl(), i);
        }
      } else {
        fAugmentations.setSize(0);
      }
    }


    /**
     * This method adds default declarations
     *
     * @see com.sun.org.apache.xerces.internal.xni.XMLAttributes#addAttribute(QName, String, String)
     */
    public int addAttribute(QName qname, String attrType, String attrValue) {
      int index = fElement.getXercesAttribute(qname.uri, qname.localpart);
      // add defaults to the tree
      if (index < 0) {
        // the default attribute was removed by a user and needed to
        // be added back
        AttrImpl attr = (AttrImpl)
            ((CoreDocumentImpl) fElement.getOwnerDocument()).createAttributeNS(
                qname.uri,
                qname.rawname,
                qname.localpart);
        // REVISIT: the following should also update ID table
        attr.setNodeValue(attrValue);
        index = fElement.setXercesAttributeNode(attr);
        fAugmentations.insertElementAt(new AugmentationsImpl(), index);
        attr.setSpecified(false);
      } else {
        // default attribute is in the tree
        // we don't need to do anything since prefix was already fixed
        // at the namespace fixup time and value must be same value, otherwise
        // attribute will be treated as specified and we will never reach
        // this method.

      }
      return index;
    }


    public void removeAllAttributes() {
      // REVISIT: implement
    }


    public void removeAttributeAt(int attrIndex) {
      // REVISIT: implement
    }


    public int getLength() {
      return (fAttributes != null) ? fAttributes.getLength() : 0;
    }


    public int getIndex(String qName) {
      // REVISIT: implement
      return -1;
    }

    public int getIndex(String uri, String localPart) {
      // REVISIT: implement
      return -1;
    }

    public void setName(int attrIndex, QName attrName) {
      // REVISIT: implement
    }

    public void getName(int attrIndex, QName attrName) {
      if (fAttributes != null) {
        updateQName((Node) fAttributes.getItem(attrIndex), attrName);
      }
    }

    public String getPrefix(int index) {
      // REVISIT: implement
      return null;
    }


    public String getURI(int index) {
      // REVISIT: implement
      return null;
    }


    public String getLocalName(int index) {
      // REVISIT: implement
      return null;
    }


    public String getQName(int index) {
      // REVISIT: implement
      return null;
    }

    public QName getQualifiedName(int index) {
      //return fAttributes.item(index).ge);
      return null;
    }

    public void setType(int attrIndex, String attrType) {
      // REVISIT: implement
    }


    public String getType(int index) {
      return "CDATA";
    }


    public String getType(String qName) {
      return "CDATA";
    }


    public String getType(String uri, String localName) {
      return "CDATA";
    }


    public void setValue(int attrIndex, String attrValue) {
      // REVISIT: is this desired behaviour?
      // The values are updated in the case datatype-normalization is turned on
      // in this case we need to make sure that specified attributes stay specified

      if (fAttributes != null) {
        AttrImpl attr = (AttrImpl) fAttributes.getItem(attrIndex);
        boolean specified = attr.getSpecified();
        attr.setValue(attrValue);
        attr.setSpecified(specified);

      }
    }

    public void setValue(int attrIndex, String attrValue, XMLString value) {
      setValue(attrIndex, value.toString());
    }

    public String getValue(int index) {
      return (fAttributes != null) ? fAttributes.item(index).getNodeValue() : "";

    }


    public String getValue(String qName) {
      // REVISIT: implement
      return null;
    }


    public String getValue(String uri, String localName) {
      if (fAttributes != null) {
        Node node = fAttributes.getNamedItemNS(uri, localName);
        return (node != null) ? node.getNodeValue() : null;
      }
      return null;
    }


    public void setNonNormalizedValue(int attrIndex, String attrValue) {
      // REVISIT: implement

    }


    public String getNonNormalizedValue(int attrIndex) {
      // REVISIT: implement
      return null;
    }


    public void setSpecified(int attrIndex, boolean specified) {
      AttrImpl attr = (AttrImpl) fAttributes.getItem(attrIndex);
      attr.setSpecified(specified);
    }

    public boolean isSpecified(int attrIndex) {
      return ((Attr) fAttributes.getItem(attrIndex)).getSpecified();
    }

    public Augmentations getAugmentations(int attributeIndex) {
      return (Augmentations) fAugmentations.elementAt(attributeIndex);
    }

    public Augmentations getAugmentations(String uri, String localPart) {
      // REVISIT: implement
      return null;
    }

    public Augmentations getAugmentations(String qName) {
      // REVISIT: implement
      return null;
    }

    /**
     * Sets the augmentations of the attribute at the specified index.
     *
     * @param attrIndex The attribute index.
     * @param augs The augmentations.
     */
    public void setAugmentations(int attrIndex, Augmentations augs) {
      fAugmentations.setElementAt(augs, attrIndex);
    }
  }

  //
  // XMLDocumentHandler methods
  //

  /**
   * The start of the document.
   *
   * @param locator The document locator, or null if the document location cannot be reported during
   * the parsing of this document. However, it is <em>strongly</em> recommended that a locator be
   * supplied that can at least report the system identifier of the document.
   * @param encoding The auto-detected IANA encoding name of the entity stream. This value will be
   * null in those situations where the entity encoding is not auto-detected (e.g. internal entities
   * or a document entity that is parsed from a java.io.Reader).
   * @param namespaceContext The namespace context in effect at the start of this document. This
   * object represents the current context. Implementors of this class are responsible for copying
   * the namespace bindings from the the current context (and its parent contexts) if that
   * information is important.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startDocument(XMLLocator locator, String encoding,
      NamespaceContext namespaceContext,
      Augmentations augs)
      throws XNIException {
  }

  /**
   * Notifies of the presence of an XMLDecl line in the document. If
   * present, this method will be called immediately following the
   * startDocument call.
   *
   * @param version The XML version.
   * @param encoding The IANA encoding name of the document, or null if not specified.
   * @param standalone The standalone value, or null if not specified.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void xmlDecl(String version, String encoding, String standalone, Augmentations augs)
      throws XNIException {
  }

  /**
   * Notifies of the presence of the DOCTYPE line in the document.
   *
   * @param rootElement The name of the root element.
   * @param publicId The public identifier if an external DTD or null if the external DTD is
   * specified using SYSTEM.
   * @param systemId The system identifier if an external DTD, null otherwise.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void doctypeDecl(String rootElement, String publicId, String systemId, Augmentations augs)
      throws XNIException {
  }

  /**
   * A comment.
   *
   * @param text The text in the comment.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by application to signal an error.
   */
  public void comment(XMLString text, Augmentations augs) throws XNIException {
  }

  /**
   * A processing instruction. Processing instructions consist of a
   * target name and, optionally, text data. The data is only meaningful
   * to the application.
   * <p>
   * Typically, a processing instruction's data will contain a series
   * of pseudo-attributes. These pseudo-attributes follow the form of
   * element attributes but are <strong>not</strong> parsed or presented
   * to the application as anything other than text. The application is
   * responsible for parsing the data.
   *
   * @param target The target.
   * @param data The data or null if none specified.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void processingInstruction(String target, XMLString data, Augmentations augs)
      throws XNIException {
  }

  /**
   * The start of an element.
   *
   * @param element The name of the element.
   * @param attributes The element attributes.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startElement(QName element, XMLAttributes attributes, Augmentations augs)
      throws XNIException {
    Element currentElement = (Element) fCurrentNode;
    int attrCount = attributes.getLength();
    if (DEBUG_EVENTS) {
      System.out.println("==>startElement: " + element +
          " attrs.length=" + attrCount);
    }

    for (int i = 0; i < attrCount; i++) {
      attributes.getName(i, fAttrQName);
      Attr attr = null;

      attr = currentElement.getAttributeNodeNS(fAttrQName.uri, fAttrQName.localpart);
      AttributePSVI attrPSVI =
          (AttributePSVI) attributes.getAugmentations(i).getItem(Constants.ATTRIBUTE_PSVI);

      if (attrPSVI != null) {
        //REVISIT: instead we should be using augmentations:
        // to set/retrieve Id attributes
        XSTypeDefinition decl = attrPSVI.getMemberTypeDefinition();
        boolean id = false;
        if (decl != null) {
          id = ((XSSimpleType) decl).isIDType();
        } else {
          decl = attrPSVI.getTypeDefinition();
          if (decl != null) {
            id = ((XSSimpleType) decl).isIDType();
          }
        }
        if (id) {
          ((ElementImpl) currentElement).setIdAttributeNode(attr, true);
        }

        if (fPSVI) {
          ((PSVIAttrNSImpl) attr).setPSVI(attrPSVI);
        }
        if ((fConfiguration.features & DOMConfigurationImpl.DTNORMALIZATION) != 0) {
          // datatype-normalization
          // NOTE: The specified value MUST be set after we set
          //       the node value because that turns the "specified"
          //       flag to "true" which may overwrite a "false"
          //       value from the attribute list.
          boolean specified = attr.getSpecified();
          attr.setValue(attrPSVI.getSchemaNormalizedValue());
          if (!specified) {
            ((AttrImpl) attr).setSpecified(specified);
          }
        }
      }
    }
  }


  /**
   * An empty element.
   *
   * @param element The name of the element.
   * @param attributes The element attributes.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void emptyElement(QName element, XMLAttributes attributes, Augmentations augs)
      throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>emptyElement: " + element);
    }

    startElement(element, attributes, augs);
    endElement(element, augs);
  }

  /**
   * This method notifies the start of a general entity.
   * <p>
   * <strong>Note:</strong> This method is not called for entity references
   * appearing as part of attribute values.
   *
   * @param name The name of the general entity.
   * @param identifier The resource identifier.
   * @param encoding The auto-detected IANA encoding name of the entity stream. This value will be
   * null in those situations where the entity encoding is not auto-detected (e.g. internal entities
   * or a document entity that is parsed from a java.io.Reader).
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startGeneralEntity(String name,
      XMLResourceIdentifier identifier,
      String encoding,
      Augmentations augs) throws XNIException {
  }

  /**
   * Notifies of the presence of a TextDecl line in an entity. If present,
   * this method will be called immediately following the startEntity call.
   * <p>
   * <strong>Note:</strong> This method will never be called for the
   * document entity; it is only called for external general entities
   * referenced in document content.
   * <p>
   * <strong>Note:</strong> This method is not called for entity references
   * appearing as part of attribute values.
   *
   * @param version The XML version, or null if not specified.
   * @param encoding The IANA encoding name of the entity.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void textDecl(String version, String encoding, Augmentations augs) throws XNIException {
  }

  /**
   * This method notifies the end of a general entity.
   * <p>
   * <strong>Note:</strong> This method is not called for entity references
   * appearing as part of attribute values.
   *
   * @param name The name of the entity.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endGeneralEntity(String name, Augmentations augs) throws XNIException {
  }

  /**
   * Character content.
   *
   * @param text The content.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void characters(XMLString text, Augmentations augs) throws XNIException {
  }

  /**
   * Ignorable whitespace. For this method to be called, the document
   * source must have some way of determining that the text containing
   * only whitespace characters should be considered ignorable. For
   * example, the validator can determine if a length of whitespace
   * characters in the document are ignorable based on the element
   * content model.
   *
   * @param text The ignorable whitespace.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void ignorableWhitespace(XMLString text, Augmentations augs) throws XNIException {
    allWhitespace = true;
  }

  /**
   * The end of an element.
   *
   * @param element The name of the element.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endElement(QName element, Augmentations augs) throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>endElement: " + element);
    }

    if (augs != null) {
      ElementPSVI elementPSVI = (ElementPSVI) augs.getItem(Constants.ELEMENT_PSVI);
      if (elementPSVI != null) {
        ElementImpl elementNode = (ElementImpl) fCurrentNode;
        if (fPSVI) {
          ((PSVIElementNSImpl) fCurrentNode).setPSVI(elementPSVI);
        }
        // include element default content (if one is available)
        String normalizedValue = elementPSVI.getSchemaNormalizedValue();
        if ((fConfiguration.features & DOMConfigurationImpl.DTNORMALIZATION) != 0) {
          if (normalizedValue != null) {
            elementNode.setTextContent(normalizedValue);
          }
        } else {
          // NOTE: this is a hack: it is possible that DOM had an empty element
          // and validator sent default value using characters(), which we don't
          // implement. Thus, here we attempt to add the default value.
          String text = elementNode.getTextContent();
          if (text.length() == 0) {
            // default content could be provided
            if (normalizedValue != null) {
              elementNode.setTextContent(normalizedValue);
            }
          }
        }
      }
    }
  }


  /**
   * The start of a CDATA section.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startCDATA(Augmentations augs) throws XNIException {
  }

  /**
   * The end of a CDATA section.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endCDATA(Augmentations augs) throws XNIException {
  }

  /**
   * The end of the document.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endDocument(Augmentations augs) throws XNIException {
  }


  /**
   * Sets the document source.
   */
  public void setDocumentSource(XMLDocumentSource source) {
  }


  /**
   * Returns the document source.
   */
  public XMLDocumentSource getDocumentSource() {
    return null;
  }


}  // DOMNormalizer class
